/*
 *  Netzpult - Your desk on the web
 *  Copyright (C) 2011  Ulrich Hilger, http://uhilger.de
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/
 */

package de.uhilger.netzpult.client;

import java.util.Iterator;
import java.util.List;

import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.DecoratedPopupPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;

import de.uhilger.netzpult.shared.Document;
import de.uhilger.netzpult.shared.Folder;


/**
 * 
 * @author Copyright (c) Ulrich Hilger, http://uhilger.de
 * @author Published under the terms and conditions of the <a
 *         href="http://www.gnu.org/licenses/" target="_blank">GNU General
 *         Public License</a>
 */
public class BoundTree extends Tree {

	private BoundTreeServiceAsync service;
	private TreeItem selectedTreeItem;
	private String owner;
	private List<Folder> path;
	private int nextPathEntry;
	private TreeItem parentItem;
	private int documentId;
	private boolean showFolderId;
	private DecoratedPopupPanel popup;
	
	public BoundTree(String owner, final boolean showDocuments, final boolean showFolderId) {
		super(new TreeBundle(showDocuments), showDocuments);
		this.showFolderId = showFolderId;
		this.owner = owner;
		popup = new DecoratedPopupPanel(true);
		addSelectionHandler(new SelectionHandler<TreeItem>() {
			public void onSelection(SelectionEvent<TreeItem> event) {
				selectedTreeItem = event.getSelectedItem();
				if(showFolderId) {
					showFolderId();
				}
			}
		});
		addOpenHandler(new OpenHandler<TreeItem>() {
			public void onOpen(OpenEvent<TreeItem> event) {
				TreeItem item = (TreeItem) event.getTarget();
				if (item.getChildCount() <= 1) {
					//Window.alert("open " + item.getText());
					item.setState(false, false);
					getChildren(item, showDocuments, null);
					item.setState(true, false);
				}
			}
		});
		/*addMouseOverHandler(new MouseOverHandler(){

			@Override
			public void onMouseOver(MouseOverEvent event) {
			}
		});
		addMouseOutHandler(new MouseOutHandler(){

			@Override
			public void onMouseOut(MouseOutEvent event) {
				popup.hide();
			}
		});*/
		ensureSelectedItemVisible();
	}
	
private void showFolderId() {
	if(selectedTreeItem != null) {
		Object userObject = selectedTreeItem.getUserObject();
		if(userObject != null && userObject instanceof Folder) {
			Folder folder = (Folder) userObject;
			popup.setWidget(new HTML(folder.getId() + " " + folder.getName()));
			//popup.setPopupPosition(event.getClientX(), event.getClientY());
			popup.show();
			Timer t = new Timer() {
	      @Override
	      public void run() {
	        popup.hide();
	      }
	    };

	    // Schedule the timer to run once in 5 seconds.
	    t.schedule(2000);
		}
	}

}

	public BoundTree(String owner, final boolean showDocuments) {
		this(owner, showDocuments, false);
	}

	public BoundTree(final boolean showDocuments) {
		this(null, showDocuments, false);
	}
	
	public void setService(BoundTreeServiceAsync service) {
		this.service = service;
	}

	public BoundTreeServiceAsync getService() {
		return service;
	}

	public TreeItem getSelectedTreeItem() {
		return selectedTreeItem;
	}

	private void getChildren(final TreeItem item, boolean showDocuments, final AsyncListener listener) {
		final Folder parentFolder = (Folder) item.getUserObject();
		if (showDocuments) {
			service.getDocuments(owner, parentFolder, new AsyncCallback<List<Document>>() {
				@Override
				public void onFailure(Throwable caught) {
					Window.alert("BoundTree.getChildren getDocuments error: " + caught.getMessage());
				}

				@Override
				public void onSuccess(List<Document> result) {
					item.getChild(0).remove();
					Iterator<Document> i = result.iterator();
					while (i.hasNext()) {
						Document doc = i.next();
						TreeItem newItem = new TreeItem();
						newItem.setHTML(doc.getTitle());
						newItem.setUserObject(doc);
						item.addItem(newItem);
					}
					service.getChildren(owner, parentFolder, new AsyncCallback<List<Folder>>() {
						public void onFailure(Throwable caught) {
							Window.alert("BoundTree.getChildren getChildren error: " + caught.getMessage());
						}

						public void onSuccess(List<Folder> result) {
							addFolders(result, item);
							if (listener != null) {
								listener.isDone();
							}
						}
					});
				}
			});

		} else {
			service.getChildren(owner, parentFolder, new AsyncCallback<List<Folder>>() {
				public void onFailure(Throwable caught) {
					Window.alert("BoundTree.getChildren getChildren error: " + caught.getMessage());
				}

				public void onSuccess(List<Folder> result) {
					item.getChild(0).remove();
					addFolders(result, item);
					if (listener != null) {
						listener.isDone();
					}
				}
			});

		}
	}

	public void addFolders(List<Folder> folders, TreeItem parentItem) {
		Iterator<Folder> i = folders.iterator();
		while (i.hasNext()) {
			Folder folder = i.next();
			TreeItem newItem = new TreeItem();
			if(showFolderId) {
				newItem.setHTML(/*folder.getId() + " " + */folder.getName());
			} else {
				newItem.setHTML(folder.getName());
			}
			newItem.setUserObject(folder);
			newItem.addItem("");
			if (parentItem == null) {
				addItem(newItem); // add as root item
			} else {
				parentItem.addItem(newItem);
			}
		}
	}
	
	public void removeItem(TreeItem item) {
		//item.getParentItem().removeItem(item);
		TreeItem parent = item.getParentItem();
		parent.setState(false, false);
		parent.removeItems();
		parent.addItem("");
		getChildren(parent, false, null);
		parent.setState(true, false);
	}
	
	public boolean addFolder(TreeItem item, Folder parentFolder, Folder folder) {
		int count = -1;
		boolean found = false;
		if(item == null) {
			count = this.getItemCount();
		} else {
			count = item.getChildCount();
		}
		TreeItem child = null;
		for(int i = 0; i < count && !found; i++) {
			if(item == null) {
				child = this.getItem(i);
			} else {
				child = item.getChild(i);
			}
			Object o = child.getUserObject();
			if(o != null && o instanceof Folder) {
				Folder f = (Folder) o;
				if(f.getId() == parentFolder.getId()) {
					found = true;
					TreeItem newItem = child.addItem(folder.getName());
					newItem.setUserObject(folder);
					newItem.addItem("");
				} else {
					found = addFolder(child, parentFolder, folder);
				}
			}
		}
		return found;
	}

	/**
	 * Einen ausgew�hlten Ordner von seiner gegenw�rtigen Position eine
	 * Position nach oben bewegen.
	 * 
	 * Diese Methode ist nur f�r die Ver�nderung an der Bedienoberfl�che
	 * gedacht, wenn man eine �nderung auf dem Server an der Bedienoberfl�che
	 * nachziehen m�chte, ohne vom Server nachzuladen.
	 */
	public void upFolder() {
		if (selectedTreeItem != null) {
			TreeItem parent = selectedTreeItem.getParentItem();
			if (parent != null) {
				int index = parent.getChildIndex(selectedTreeItem);
				if (index > 1) {
					parent.removeItem(selectedTreeItem);
					parent.insertItem(index - 1, selectedTreeItem);
					setSelectedItem(selectedTreeItem);
				} else {
					Window.alert("Ordner ist schon an der ersten Position.");
				}
			}
		}
	}

	/**
	 * Einen ausgew�hlten Ordner von seiner gegenw�rtigen Position eine
	 * Position nach unten bewegen.
	 * 
	 * Diese Methode ist nur f�r die Ver�nderung an der Bedienoberfl�che
	 * gedacht, wenn man eine �nderung auf dem Server an der Bedienoberfl�che
	 * nachziehen m�chte, ohne vom Server nachzuladen.
	 */
	public void downFolder() {
		if (selectedTreeItem != null) {
			TreeItem parent = selectedTreeItem.getParentItem();
			if (parent != null) {
				int index = parent.getChildIndex(selectedTreeItem);
				if (index < parent.getChildCount() - 2) {
					parent.removeItem(selectedTreeItem);
					parent.insertItem(index + 1, selectedTreeItem);
					setSelectedItem(selectedTreeItem);
				} else if (index == parent.getChildCount() - 2) {
					parent.removeItem(selectedTreeItem);
					parent.addItem(selectedTreeItem);
					setSelectedItem(selectedTreeItem);
				} else {
					Window.alert("Ordner ist schon an der letzten Position.");
				}
			}
		}
	}

	public void openFolderPath(final List<Folder> path, int documentId) {
		// Window.alert("BoundTree.openFolderPath docId=" + documentId);
		Folder folder = path.get(path.size() - 1);
		if (folder.getId() == Folder.PUBLIC_FOLDER_ID) {
			openRootDocument(documentId);
		} else {
			this.path = path;
			this.nextPathEntry = 1;
			this.documentId = documentId;
			final TreeItem rootItem = getRootItemForFolder(path.get(0));
			parentItem = rootItem;
			getChildren(rootItem, true, new AsyncListener() {
				public void isDone() {
					rootItem.setState(true, false);
					doNext();
				}
			});
		}
	}

	public void openRootDocument(int documentId) {
		TreeItem item = getRootItemForDocument(documentId);
		if (item != null) {
			setSelectedItem(item);
		}
	}

	private void doNext() {
		int nextEntry = nextPathEntry++;
		if (nextEntry < path.size()) {
			// Window.alert("getting child for nextEntry " + nextEntry +
			// path.get(nextEntry).getName());
			TreeItem child = getChildItemForFolder(parentItem, path.get(nextEntry));
			if (child != null) {
				// Window.alert("child is " + child.getHTML());
				final TreeItem thisParent = child;
				parentItem = child;
				getChildren(child, true, new AsyncListener() {
					public void isDone() {
						// Window.alert("opening " + thisParent.getHTML());
						thisParent.setState(true, false);
						doNext();
					}
				});
			} else {
				// Window.alert("child is null for parent " + parentItem.getHTML() +
				// "folder " + path.get(nextEntry).getName());
			}
		} else {
			// Window.alert("doNext is finished, parentItem is " +
			// parentItem.getHTML());
			// openDocument(parentItem, documentId);
			TreeItem documentItem = getChildItemForDocument(parentItem, documentId);
			if (documentItem != null) {
				setSelectedItem(documentItem);
			}
		}
	}

	private TreeItem getRootItemForFolder(Folder folder) {
		int itemCount = getItemCount();
		int i = 0;
		TreeItem item = null;
		while (i < itemCount && item == null) {
			TreeItem testItem = getItem(i);
			Object o = testItem.getUserObject();
			if (o != null && o instanceof Folder) {
				Folder testFolder = (Folder) o;
				if (testFolder.getId() == folder.getId()) {
					item = testItem;
				}
			}
			i++;
		}
		return item;
	}

	private TreeItem getRootItemForDocument(int documentId) {
		int itemCount = getItemCount();
		int i = 0;
		TreeItem item = null;
		while (i < itemCount && item == null) {
			TreeItem testItem = getItem(i);
			Object o = testItem.getUserObject();
			if (o != null && o instanceof Document) {
				Document testDocument = (Document) o;
				if (testDocument.getId() == documentId) {
					item = testItem;
				}
			}
			i++;
		}
		return item;
	}

	private TreeItem getChildItemForFolder(TreeItem parent, Folder folder) {
		int itemCount = parent.getChildCount();
		// Window.alert("child count for " + parent.getHTML() + " is " + itemCount);
		int i = 0;
		TreeItem child = null;
		while (i < itemCount && child == null) {
			TreeItem testItem = parent.getChild(i);
			Object o = testItem.getUserObject();
			if (o != null && o instanceof Folder) {
				Folder testFolder = (Folder) o;
				if (testFolder.getId() == folder.getId()) {
					child = testItem;
				}
			}
			i++;
		}
		return child;
	}

	private TreeItem getChildItemForDocument(TreeItem parent, int documentId) {
		int itemCount = parent.getChildCount();
		// Window.alert("child count for " + parent.getHTML() + " is " + itemCount);
		int i = 0;
		TreeItem child = null;
		while (i < itemCount && child == null) {
			TreeItem testItem = parent.getChild(i);
			Object o = testItem.getUserObject();
			if (o != null && o instanceof Document) {
				Document testDocument = (Document) o;
				if (testDocument.getId() == documentId) {
					child = testItem;
				}
			}
			i++;
		}
		return child;
	}

}
